package org.ns.summer.core;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.ns.summer.aop.CglibProxyUtils;
import org.ns.summer.core.anns.AliasFor;

import java.lang.annotation.*;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 注解操作工具，支持注解继承
 */
public class AnnotationUtils {

    public static <T extends Annotation> T getAnnotation(String classname, ClassLoader classLoader, Class<?> resultAnnotation) throws ClassNotFoundException {

        return (T)getAnnotation(BeanUtils.createClass(classname, classLoader), resultAnnotation);
    }

    public static Annotation getAnnotation(AnnotatedElement annotatedElement, Class<?> resultAnnotation) {
        Annotation[] annotations = annotatedElement.getAnnotations();
        Annotation result = null;
        for (Annotation annotation : annotations) {
            result = getAnnotation(annotation, resultAnnotation);
            if (result != null) {
                break;
            }
        }
        return result;
    }

    public static <T extends  Annotation> T getAnnotation(Annotation[] targetAnnotations, Class<?> resultAnnotation) {
        for (Annotation targetAnnotation : targetAnnotations) {
            Annotation result = getAnnotation(targetAnnotation, resultAnnotation);
            if (result != null) {
                return (T)result;
            }
        }
        return null;
    }

    public static Annotation getAnnotation(Annotation targetAnnotation, Class<?> resultAnnotation) {
        Annotation resultAnnotationType = null;
        Class<?> targetType = targetAnnotation.annotationType();
        List<Annotation> annPath = findPath(targetAnnotation, resultAnnotation); // 匹配到路径
        if (annPath.size() == 0) { // 没有找到
            return null;
        }


        Map<String, Object> formap = new HashMap<>(); // attribute

        for (int i = annPath.size() - 1 ; i >= 0 ; i --) {
            Annotation handlerAnnotation = annPath.get(i);//
            List<Method> declaredMethods = BeanUtils.getMethods(handlerAnnotation.annotationType(), AliasFor.class);
            String handlerAnnotationTypeName = handlerAnnotation.annotationType().getTypeName();
            for (Method declaredMethod : declaredMethods) {
                String name = declaredMethod.getName();
                String key1 = handlerAnnotationTypeName + "." + name;
                Object value = formap.get(key1);
                if (value == null) {
                    value = BeanUtils.invoke(declaredMethod, handlerAnnotation); // 获得数据
                }
                if (value instanceof  String && ((String)value).isEmpty()
                || value.getClass().isArray() && Array.getLength(value) == 0) {
                    continue;
                }

                // 设置给谁
                AliasFor aliasFor = declaredMethod.getAnnotation(AliasFor.class);
                String attribute = aliasFor.attribute().isEmpty() ? name : aliasFor.attribute();
                String desAnnTypeName = aliasFor.annotation() == Annotation.class ? handlerAnnotationTypeName : aliasFor.annotation().getTypeName();
                String key2 = desAnnTypeName + "." + attribute;
                formap.putIfAbsent(key2, value);
            }
        }

        Map<String, Object> resultMap = new HashMap<>();
        String resultAnnotationName = resultAnnotation.getName();
        for (Method declaredMethod : resultAnnotation.getDeclaredMethods()) {
            String name = declaredMethod.getName();
            String key = resultAnnotationName + "." + declaredMethod.getName();
            Object value = formap.get(key);
            if (value == null) {
                value = BeanUtils.invoke(declaredMethod, annPath.get(0)); // 获得数据
            }
            resultMap.put(name, value);
        }

        return CglibProxyUtils.proxy(resultAnnotation, new AnnotationProxy(resultMap, annPath.get(0)));
    }

    private static List<Annotation> findPath(Annotation annotation, Class<?> resultAnnotation) {
        List<Annotation> path = new ArrayList<>();
        findPath(annotation, resultAnnotation, path);
        return path;
    }
    private static boolean findPath(Annotation annotation, Class<?> resultAnnotation, List<Annotation> path) {
        if (annotation == null) {
            return false;
        } else if (annotation.annotationType() == resultAnnotation) {
            path.add(annotation);
            return true;
        } else {
            Annotation[] annotations = annotation.annotationType().getAnnotations();
            for (Annotation superAnnotation : annotations) {
                if (superAnnotation.annotationType().getName().startsWith("java.lang.annotation")){

                } else if (superAnnotation.annotationType() == annotation.annotationType()) {

                } else if (findPath(superAnnotation, resultAnnotation, path)) {
                    path.add(annotation);
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * 是否存在当前注解
     * @param annotatedElement
     * @param resultAnnotation
     * @return
     */
    public static boolean existAnnotation(AnnotatedElement annotatedElement, Class<?> resultAnnotation) {
        Annotation[] annotations = annotatedElement.getAnnotations();
        for (Annotation annotation : annotations) {
            if (existSuperAnnotation(annotation, resultAnnotation)) {
                return true;
            }
        }
        return false;
    }

    public static boolean existSuperAnnotation(Annotation annotation, Class<?> resultAnnotation) {
        return existSuperAnnotation(annotation.annotationType(), resultAnnotation);
    }

    public static boolean existSuperAnnotation(Annotation[] annotations, Class<?> resultAnnotation) {
        for (Annotation annotation : annotations) {
            if (existSuperAnnotation(annotation.annotationType(), resultAnnotation)) {
                return true;
            }
        }
        return false;
    }

    public static boolean existSuperAnnotation(Class<?> annotationType, Class<?> resultAnnotation) {
        if (annotationType == null || !annotationType.isAnnotation()) {
            return false;
        } else if (annotationType == resultAnnotation) {
            return true;
        } else {
            Annotation[] annotations = annotationType.getAnnotations();
            for (Annotation superAnnotation : annotations) {
                if (superAnnotation.annotationType().getName().startsWith("java.lang.annotation")){

                } else if (superAnnotation.annotationType() == annotationType) {

                } else if (existSuperAnnotation(superAnnotation.annotationType(), resultAnnotation)) {
                    return true;
                }
            }
            return false;
        }
    }

    public  static Annotation[] findParameterAnnotations(Executable executable, Class<?> parameterAnnotationtype) {
        Annotation[][] panns = executable.getParameterAnnotations();
        Annotation[] result = new Annotation[panns.length];
        for (int i = 0; i < panns.length; i++) {
            for (Annotation annotation : panns[i]) {
                if (existSuperAnnotation(annotation, parameterAnnotationtype)) {
                    result[i] = getAnnotation(annotation, parameterAnnotationtype);
                    break;
                }
            }
        }
        return result;
    }



    public static class AnnotationProxy implements MethodInterceptor {
        private Map<String, Object> resultMap;
        private Annotation annotation;

        public AnnotationProxy(Map<String, Object> resultMap, Annotation annotation) {
            this.resultMap = resultMap;
            this.annotation = annotation;
        }

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

            String methodName = method.getName();
            Object value = resultMap.get(methodName);
            if (methodName.equals("toString") && value == null) {
                return resultMap.toString();
            }
            if (value != null && ! (value instanceof CglibProxyUtils.SummerProxy)) {
                if (value instanceof Annotation) {
                    value = getAnnotation(((Annotation) value), ((Annotation) value).annotationType());
                    resultMap.put(methodName, value);
                } else if (value instanceof Annotation[]) {
                    int length = Array.getLength(value);
                    for (int i = 0; i < length; i++) {
                        Object tmp = Array.get(value, i);
                        tmp = getAnnotation(((Annotation) tmp), ((Annotation) tmp).annotationType());
                        Array.set(value, i, tmp);
                    }
                    resultMap.put(methodName, value);
                }
            }

            return value;
        }
    }

}
